---
import { Octokit } from "@octokit/rest";
import { promisify } from "node:util";
import * as childProcess from "node:child_process";
import { stat } from "npm-stats-api";

import { GITHUB_REPO } from "../../consts";
import IconCopy from "../../icons/Copy.astro";
import { getPathParamsFromId, getTextLocalized } from "../../languages";
import { translations } from "src/languages";

const exec = promisify(childProcess.exec);

const { lang } = getPathParamsFromId(Astro.url.pathname);

const [owner, repo] = GITHUB_REPO.split("/");
const repoInfo = { owner, repo };

const octokit = new Octokit({ auth: import.meta.env.GITHUB_TOKEN });

const formatDate = (date: Date) => {
  const month = (date.getMonth() + 1).toString().padStart(2, "0");
  const day = date.getDate().toString().padStart(2, "0");
  return `${date.getUTCFullYear()}-${month}-${day}`;
};

async function getNpmStats() {
  const to = new Date();
  const from = new Date("2018-01-17T03:17:09.763Z");
  const fromDateString = formatDate(from);
  const toDateString = formatDate(to);

  try {
    const { body: downloadCounts } = await stat("effector", fromDateString, toDateString);

    const result = await exec("npm info effector --json");
    const npm = JSON.parse(result.stdout);
    const latest = npm["dist-tags"].latest as string;

    return {
      latest,
      downloads: `${(downloadCounts.downloads / 1000).toFixed(0)}K`,
    };
  } catch (error) {
    console.error("Failed to get NPM stats", error);
    return null;
  }
}

async function getRepoStats() {
  const { data: repo } = await octokit.repos.get(repoInfo);

  let contributorsCount = 0;
  let page = 1;
  while (true) {
    const { data: pageContributors } = await octokit.repos.listContributors({
      ...repoInfo,
      per_page: 100,
      page,
    });
    contributorsCount += pageContributors.length;

    if (pageContributors.length < 100) {
      break;
    }
    page++;
  }

  return {
    stars: repo.stargazers_count.toLocaleString("en-US"),
    contributors: contributorsCount,
    license: repo.license?.spdx_id,
  };
}

async function getGzipSize() {
  try {
    const response = await fetch("https://bundlephobia.com/api/size?package=effector@latest");

    if (response.ok) {
      const { gzip } = await response.json();

      if (!gzip) {
        throw new Error("No gzip size in the bundlephobia answer");
      }

      return {
        size: `${(gzip / 1024).toFixed(1)} kB`,
      };
    } else {
      console.error(`Bundlephobia answer is not OK: ${response.statusText}`);
      return { size: "11.9 kB" };
    }
  } catch (error) {
    console.error("Failed to fetch gzip size:", error);
  }
}

const statsMeta = {
  size: {
    label: getTextLocalized(translations.Landing.Stats.size, lang),
    link: "https://bundlephobia.com/package/effector@latest",
  },
  contributors: {
    label: getTextLocalized(translations.Landing.Stats.contributors, lang),
    link: "https://github.com/effector/effector/graphs/contributors",
  },
  stars: {
    label: getTextLocalized(translations.Landing.Stats.stars, lang),
    link: "https://star-history.com/#effector/effector&Date",
  },
  downloads: {
    label: getTextLocalized(translations.Landing.Stats.downloads, lang),
    link: "https://npmtrends.com/effector",
  },
  latest: {
    label: getTextLocalized(translations.Landing.Stats.latest, lang),
    link: "https://github.com/effector/effector/releases",
  },
  license: {
    label: getTextLocalized(translations.Landing.Stats.license, lang),
    link: "https://raw.githubusercontent.com/effector/effector/master/LICENSE",
  },
};

const gettingStartedText = getTextLocalized(
  translations.Landing.Stats.StartByAddingEffectorAsDependency,
  lang,
);

const stats = await Promise.all([getRepoStats(), getNpmStats(), getGzipSize()]).then(
  ([repo, npm, size]) => ({ ...repo, ...npm, ...size }),
);
---

<div class="wrapper">
  <h3>
    {getTextLocalized(translations.Landing.Stats.title, lang)}
  </h3>
  <div class="stats">
    <div class="counts">
      {
        Object.entries(stats)
          .filter(([_name, counter]) => typeof counter !== "undefined")
          .map(([name, counter]) => {
            return (
              <a
                class="stats-card"
                href={statsMeta[name as keyof typeof stats].link}
                target="_blank"
                rel="noreferrer noopener"
              >
                <span class="text-4xl">{counter}</span>
                <span>{statsMeta[name as keyof typeof stats].label}</span>
              </a>
            );
          })
      }
    </div>
    <div class="stats-bar">
      <span class="getting-started-message">{gettingStartedText}:</span>
      <div class="install-command">
        <span class="inline-block">
          <span class="command-line-sign">$&nbsp;</span>npm install <span class="package-name"
            >effector</span
          >
        </span>
        <copy-button class="copy-button" data-value="npm install effector" aria-role="button">
          <IconCopy />
        </copy-button>
      </div>
    </div>
  </div>
</div>

<style>
  .wrapper {
    @apply mx-auto max-w-6xl px-3 py-8 sm:px-6 sm:py-12;
  }

  .wrapper h3 {
    @apply mb-4 text-4xl sm:mb-10 lg:mb-6;
    -webkit-box-decoration-break: clone;
    -webkit-text-fill-color: transparent;
    background: linear-gradient(to right bottom, var(--theme-text-light) 30%, var(--theme-text));
    -webkit-background-clip: text;
    line-height: 1.3;
    text-align: center;
  }

  .stats {
    @apply min-h-[4rem] rounded-3xl bg-[var(--theme-card)] p-4 shadow-lg;
  }

  .counts {
    @apply mb-6 grid select-none grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6;
  }

  .stats-card {
    @apply flex min-w-[8rem] flex-col items-center rounded-lg p-2.5 text-[color:var(--theme-text)] focus:no-underline;
    @apply transition-all hover:bg-[var(--theme-accent)] hover:text-[color:var(--theme-bg)] hover:shadow-xl focus:bg-[var(--theme-accent)];
  }

  .stats-bar {
    @apply flex flex-wrap items-center justify-between gap-6 md:pl-2;
  }

  .getting-started-message {
    @apply hidden select-none md:inline;
  }

  .command-line-sign {
    @apply select-none text-[color:var(--theme-text-light)];
  }

  .package-name {
    color: var(--theme-text-accent);
  }

  .install-command {
    @apply flex flex-1 items-center justify-between rounded-lg bg-[var(--theme-bg)] p-2 pl-4 font-mono md:max-w-xl;
  }

  .copy-button {
    @apply flex cursor-pointer items-center justify-center rounded-md bg-[var(--theme-divider)] p-1;
    @apply transition-all hover:bg-[var(--theme-accent)] hover:text-[color:var(--theme-bg)] hover:shadow-md;
  }

  .copy-button:global(.copied) {
    @apply animate-ping;
    animation-iteration-count: 1;
    animation-duration: 400ms;
  }
</style>

<script>
  class CopyButton extends HTMLElement {
    constructor() {
      super();

      const copyValue = async () => {
        try {
          await navigator.clipboard.writeText(this.dataset.value!);
          this.classList.add("copied");
          setTimeout(() => {
            this.classList.remove("copied");
          }, 300);
        } catch (error) {
          console.error("Failed to copy text:", error);
        }
      };

      this.addEventListener("click", copyValue);
    }
  }

  customElements.define("copy-button", CopyButton);
</script>
